import MapMonitor from "../collection/MapMonitor"
import { MeasurementContentsReplace } from "../command/measurements.command"
import ApplicationData from "../data/ApplicationData"
import MeasurementModeData from "../data/MeasurementModeData"
import PlayerOptionsData from "../data/PlayerOptionsData"
import EngineContext from "../EngineContext"
import aggregationTypeEnum from "../enum/aggregationType.enum"
import dataTypeEnum from "../enum/dataType.enum"
import measurementEnum from "../enum/measurement.enum"
import storageTypeEnum from "../enum/storageType.enum"
import T from "../math/19674"
import Measurement from "../object/Measurement"
import { createArray } from "../observer/observable.array"
import ObservableMap, { createMap } from "../observer/observable.map"
import { CollectionSync } from "../storage/DataSync"
import DataSyncStore from "../storage/DataSyncStore"
import Store from "../storage/Store"
import SymbolList from "../SymbolList"
import { getDate } from "../utils/date.util"
import funcUtil from "../utils/func.util"
import Grouper from "../utils/Grouper"
import Logger from "../utils/Logger"
import Module from "./Module"

const g = new Logger("measure->group")
const f = (e, t?) => {
  if (
    (t
      ? (t.pointGroups.reset(), (t.lookup.idToGroup = {}), (t.lookup.groupToIds = {}), (t.lookup.groupCount = 0))
      : (t = {
          pointGroups: new Grouper(createArray([])),
          lookup: { idToGroup: {}, groupToIds: {}, groupCount: 0 }
        }),
    e.length > 0)
  ) {
    e.sort((e, t) => 100 * (e.group + 1) + e.groupOrder - (100 * (t.group + 1) + t.groupOrder))
    for (const i of e) {
      const e = 0 === t.pointGroups.groupCount || i.group >= t.pointGroups.groupCount
      v(i, t.pointGroups, e, t.lookup)
    }
    Logger.level >= 4 &&
      (g.debug(
        "sorce",
        e.slice().map(e => `${e.sid},${e.group},${e.groupOrder}`)
      ),
      g.debug(t.pointGroups.toString()))
  }
  return t
}
const v = (e, t, i, n) => {
  if (i) {
    const { floorId: i, roomId: s, text: r, type: o, sid: a, visible: l, created: c, modified: h } = e,
      d = {
        floorId: i,
        roomId: s,
        text: r,
        type: o,
        sid: a,
        visible: l,
        created: c,
        modified: h
      }
    t.startGroup(d), t.push(e.start_position), n.groupCount++
  }
  const s = t.groupCount - 1
  t.push(e.end_position), (n.idToGroup[e.sid] = s), n.groupToIds[s] || (n.groupToIds[s] = []), n.groupToIds[s].push(e.sid)
}
const M = new Logger("measurement-serialization")
const deserialize = e => {
  if (!e) return null
  const t = {}
  try {
    if (e)
      for (const i of Object.keys(e)) {
        const n = e[i]
        if (!n || !n.version || isNaN(Number.parseFloat(n.version)) || Number.parseFloat(n.version) < 3) continue
        const s = "type" in n ? n.type : measurementEnum.FloorplanOnly,
          r = new Measurement(),
          o = void 0 !== n.floor && -1 !== n.floor ? String(n.floor) : "",
          a = n.room ? String(n.room) : null
        t[n.sid] = r.copy({
          start_position: T.gj(n.start_position),
          end_position: T.gj(n.end_position),
          type: s,
          sid: n.sid,
          text: n.text || "",
          visible: n.visible,
          floorId: o,
          created: getDate(n.created),
          modified: getDate(n.modified),
          version: r.version,
          roomId: a,
          group: void 0 !== n.group ? n.group : -1,
          groupOrder: void 0 !== n.groupOrder ? n.groupOrder : -1
        })
      }
  } catch (e) {
    return M.debug("Error parsing measurement data;", e), null
  }
  return t
}

const k = new Logger("MeasurementData")
export default class MeasurementDataModule extends Module {
  idLookup: Map<string, string[]>
  tryInferMeshIds: (e: any) => void
  meshQueryModule: any
  tryInferGroups: (e: any) => void
  engine: EngineContext
  config: any
  measurementModeData: MeasurementModeData
  applicationData: ApplicationData
  store: DataSyncStore
  measurementMap: ObservableMap<Measurement>
  constructor() {
    super(...arguments)
    this.name = "measurements-data"
    this.idLookup = new Map()
    this.tryInferMeshIds = e => {
      for (const t in e) {
        const i = e[t]
        ;(i.type !== measurementEnum.ThreeD && i.type !== measurementEnum.FloorplanOnly) || this.meshQueryModule.inferMeshIdsFromPoint(i, i.start_position)
      }
    }
    this.tryInferGroups = e => {
      const t = Object.keys(e)
      if (t.every(t => void 0 !== e[t].group && -1 !== e[t].group)) return
      let i = 0,
        n = 0,
        s = 0
      const r = {},
        o = {},
        a = new Set(),
        l = e => `${e.x.toFixed(5)},${e.y.toFixed(5)},${e.z.toFixed(5)}`
      for (const c of t) {
        const t = e[c],
          h = l(t.start_position),
          d = l(t.end_position)
        a.has(h) ? ((n = r[h]), (s = e[o[h]].groupOrder + 1)) : a.has(d) ? ((n = r[d]), (s = e[o[d]].groupOrder + 1)) : ((n = i++), (s = 0)),
          a.has(h) || a.add(h),
          a.has(d) || a.add(d),
          (r[h] = n),
          (r[d] = n),
          (o[h] = t.sid),
          (o[d] = t.sid),
          (t.group = n),
          (t.groupOrder = s),
          t.commit()
      }
    }
  }
  async init(e, t: EngineContext) {
    this.engine = t
    const { readonly: i } = e
    const n = [this.tryInferMeshIds, this.tryInferGroups, this.updateIdLookup.bind(this)]
    const l = await t.market.waitForData(PlayerOptionsData)
    //measurements 彭蔚
    this.config = e
    this.meshQueryModule = await t.getModuleBySymbol(SymbolList.MESH_QUERY)
    this.measurementModeData = await t.market.waitForData(MeasurementModeData)
    this.applicationData = await t.market.waitForData(ApplicationData)
    const u = l.options.measurements && l.options.measurements_saved
    if (i && !u) return
    this.store = this.configureStorage(this.config)
    const p = (await this.store.read()) || {}
    if ((n.forEach(e => e(p)), (this.measurementMap = createMap(p)), !i)) {
      const e = new MapMonitor(this.measurementMap, { aggregationType: aggregationTypeEnum.NextFrame }, t)
      //有问题
      this.store.addMonitor(e), this.bindings.push(this.applicationData.onPropertyChanged("application", () => this.onApplicationChanged()))
    }
    this.onApplicationChanged()
  }
  configureStorage(e) {
    const { modelId, baseUrl, queue, readonly } = e
    k.info(`Loading Measurements from ${storageTypeEnum.JSONSTORE}`)
    const path = `${baseUrl}/scene-portal/${modelId}/api/measurements`
    const store = new Store.ReadOnlyStore({ queue, path, deserialize })
    const sync = new CollectionSync({ publishStores: [store] })
    return new DataSyncStore(sync, {
      dataType: dataTypeEnum.MEASUREMENTS,
      engine: this.engine
    })
  }
  onApplicationChanged() {
    const e = this.measurementMap.values.map(e => new Measurement().copy(e))
    this.engine.commandBinder.issueCommand(new MeasurementContentsReplace({ points: f(e).pointGroups }))
  }
  updateMeasurementsFromGroups() {
    const e = new Set(this.measurementMap.keys)
    const t = new Set()
    for (const e of this.measurementModeData.groups())
      for (let i = 0; i < e.count; i++) {
        const n = e.get(i)
        const s = e.has(i + 1) && e.get(i + 1)
        if (!s) continue
        const r = this.idLookup.get(e.info.sid)
        let o = r && i in r ? r[i] : 0 === i ? e.info.sid : funcUtil.randomStr()
        t.add(o)
        const a = new Measurement().copy({
          sid: o,
          start_position: n,
          end_position: s,
          floorId: e.info.floorId,
          roomId: e.info.roomId,
          group: e.index,
          groupOrder: i,
          text: e.info.text,
          visible: e.info.visible,
          version: "3.2",
          type: e.info.type,
          created: e.info.created,
          modified: e.info.modified
        })
        this.measurementMap.has(o)
          ? this.measurementMap.get(o).copy(a)
          : (k.debug(`Added Measurement ${o} in group ${e.index} (${i})`), this.measurementMap.set(o, a))
      }
    for (const i of e) t.has(i) || (k.debug(`Removed Measurement ${i}`), this.measurementMap.delete(i))
    this.updateIdLookup(this.measurementMap.values)
  }
  updateIdLookup(e: Measurement[]) {
    const t: Record<number, string> = Object.values(e).reduce((e, t: Measurement) => (0 === t.groupOrder && (e[t.group] = t.sid), e), {})
    const i: Measurement[] = Object.values(e).sort((e: Measurement, t: Measurement) => e.group - t.group || e.groupOrder - t.groupOrder)
    const n = new Map<string, string[]>()
    for (const e of i) {
      const i = t[e.group]
      if (void 0 === i) {
        k.warn(`Group ${e.group} started with unexpected index ${e.groupOrder}`)
        continue
      }
      const s = n.get(i) || []
      s.push(e.sid)
      n.set(i, s)
    }
    this.idLookup = n
  }
  onUpdate(e) {}
}
